#pragma once
#include <condition_variable>
#include <mutex>

class RWLock
{
   public:
    void rlock()
    {
        std::unique_lock<std::mutex> ul(mtx_);
        if (status_ < 0)
        {
            ++read_blocked_count_;
            cv_read_.wait(ul, [&]() -> bool { return write_blocked_count_ == 0 && status_ >= 0; });
            --read_blocked_count_;
        }
        ++status_;
    }

    void wlock()
    {
        std::unique_lock<std::mutex> ul(mtx_);
        if (status_ != 0)
        {
            ++write_blocked_count_;
            cv_write_.wait(ul, [&]() -> bool { return status_ == 0; });
            --write_blocked_count_;
        }
        status_ = -1;
    }
    void unlock()
    {
        std::lock_guard<std::mutex> lg(mtx_);
        if (status_ < 0)
        {
            status_ = 0;
        }
        else if (status_ > 0)
        {
            --status_;
        }
        else  // status_ == 0 invalid call
        {
            return;
        }

        if (status_ == 0)
        {
            if (write_blocked_count_ > 0)
            {
                cv_write_.notify_one();
            }
            else if (read_blocked_count_ > 0)
            {
                cv_read_.notify_all();
            }
        }
    }

   private:
    volatile int read_blocked_count_ = 0;
    volatile int write_blocked_count_ = 0;

    // -1    : one writer
    // 0     : no reader and no writer
    // n > 0 : n reader
    volatile int status_ = 0;

    std::condition_variable cv_read_;
    std::condition_variable cv_write_;
    std::mutex mtx_;
};